// i.d(t, { K: () => SensorModule })
import * as THREE from "three"
import CameraData from "../data/CameraData"
import { SourceTypeMin } from "../enum/sourceType.enum"
import { ClickEvent, HoverEvent, UnhoverEvent } from "../event/GestureEvent"
import PureObservable from "../observer/pure.observer"
import SymbolList from "../SymbolList"
import Matrix from "../three/Matrix"
import Vectors from "../three/Vectors"
import Comparator from "../utils/Comparator"
import Module from "./Module"
import RaycasterModule from "./RaycasterModule"
class SensorObserver {
  needsUpdate: boolean
  constructor() {
    this.needsUpdate = !0
  }
  notify() {
    this.needsUpdate = !0
  }
}
class SensorObservable {
  observable: PureObservable
  notifier: any
  dependencySub: any
  constructor(e) {
    ;(this.observable = new PureObservable()),
      (this.notifier = new (class {
        sensorObservable: any
        constructor(e) {
          this.sensorObservable = e
        }
        notify() {
          this.sensorObservable.observable.notify()
        }
      })(this)),
      (this.dependencySub = e.sub(this.notifier))
  }
  dispose() {
    this.dependencySub.cancel()
  }
  observe(e) {
    return this.observable.observe(e)
  }
}
class CameraFrustumVolume {
  cameraData: any
  _forward: THREE.Vector3
  frustum: THREE.Frustum
  frustumTransform: Matrix
  frustumProjection: Matrix
  forwardObserver: SensorObserver
  frustumObserver: SensorObserver
  frustumObservable: SensorObservable
  constructor(e) {
    ;(this.cameraData = e),
      (this._forward = new THREE.Vector3()),
      (this.frustum = new THREE.Frustum()),
      (this.frustumTransform = new Matrix()),
      (this.frustumProjection = new Matrix()),
      (this.forwardObserver = new SensorObserver()),
      (this.frustumObserver = new SensorObserver()),
      (this.frustumObservable = new SensorObservable(new CameraDataSubsriber(e))),
      this.frustumObservable.observe(this.forwardObserver),
      this.frustumObservable.observe(this.frustumObserver)
  }
  dispose() {
    this.frustumObservable.dispose()
  }
  get fovx() {
    return this.cameraData.fovX()
  }
  get fovy() {
    return this.cameraData.fovY()
  }
  get forward() {
    return (
      this.forwardObserver.needsUpdate &&
        ((this.forwardObserver.needsUpdate = !1), this._forward.copy(Vectors.FORWARD), this._forward.applyQuaternion(this.cameraData.pose.rotation)),
      this._forward
    )
  }
  observe(e) {
    return this.frustumObservable.observe(e)
  }
  get origin() {
    return this.cameraData.pose.position
  }
  containsPoint(e) {
    return this.updateVolume(), this.frustum.containsPoint(e)
  }
  updateVolume() {
    if (this.frustumObserver.needsUpdate) {
      this.frustumObserver.needsUpdate = !1
      const e = this.cameraData.pose
      this.frustumTransform.compose(e.position, e.rotation, Vectors.UNIT).getInverse(this.frustumTransform),
        this.frustumProjection.copy(this.cameraData.pose.projection),
        this.frustum.setFromProjectionMatrix(this.frustumTransform.multiplyMatrices(this.frustumProjection, this.frustumTransform).asThreeMatrix4())
    }
  }
}
class CameraDataSubsriber {
  cameraData: any
  constructor(e) {
    this.cameraData = e
  }
  sub(e) {
    return this.cameraData.onChanged(() => e.notify())
  }
}
class SensorUpdater {
  sensors: Set<any>
  constructor() {
    this.sensors = new Set()
  }
  addSensor(e) {
    this.sensors.add(e)
  }
  removeSensor(e) {
    this.sensors.delete(e)
  }
  update() {
    for (const e of this.sensors) e.update()
  }
}
class SensorDecorator {
  frustum: any
  raycaster: any
  sensorDisposer: any
  volumeObserver: SensorObserver
  sources: Set<unknown>
  dirtySources: Set<unknown>
  sourceWatchers: Map<any, any>
  readingsCache: Map<any, any>
  readingObservable: PureObservable
  raycastDirection: THREE.Vector3
  volumeSub: any
  constructor(e, t, i) {
    ;(this.frustum = e),
      (this.raycaster = t),
      (this.sensorDisposer = i),
      (this.volumeObserver = new SensorObserver()),
      (this.sources = new Set()),
      (this.dirtySources = new Set()),
      (this.sourceWatchers = new Map()),
      (this.readingsCache = new Map()),
      (this.readingObservable = new PureObservable()),
      (this.raycastDirection = new THREE.Vector3()),
      (this.volumeSub = e.observe(this.volumeObserver))
  }
  get origin() {
    return this.frustum.origin
  }
  dispose() {
    this.volumeSub.cancel(), this.sensorDisposer.disposeSensor(this)
  }
  addSource(...e) {
    for (const t of e) this.sources.add(t), this.dirtySources.add(t), this.sourceWatchers.set(t, new SourceWatcher(this.dirtySources, t))
  }
  get readings() {
    return this.readingsCache
  }
  update() {
    this.volumeObserver.needsUpdate
      ? ((this.volumeObserver.needsUpdate = !1), this.recomputeAllReadings(), this.readingObservable.notify())
      : this.dirtySources.size > 0 && (this.recomputeDirtyReadings(), this.readingObservable.notify())
  }
  recomputeAllReadings() {
    for (const e of this.sources) this.recomputeReading(e)
    this.dirtySources.clear()
  }
  recomputeDirtyReadings() {
    for (const e of this.dirtySources) this.recomputeReading(e)
    this.dirtySources.clear()
  }
  recomputeReading(e) {
    this.raycastDirection.copy(e.origin).sub(this.frustum.origin).normalize()
    const t = this.frustum.origin.distanceToSquared(e.origin),
      i = this.readingsCache.get(e) || { direction: new THREE.Vector3() }
    ;(i.inRange = (function (e, t) {
      return t.volume.containsPoint(e.origin)
    })(this, e)),
      (i.inView =
        i.inRange &&
        this.frustum.containsPoint(e.origin) &&
        !(function (e, t) {
          return !!t && e > t.distance ** 2
        })(t, this.raycaster.picking.pick(this.frustum.origin, this.raycastDirection))),
      i.direction.copy(this.raycastDirection),
      (i.distanceSq = t),
      this.readingsCache.set(e, i)
  }
  onReadingsUpdated(e) {
    return this.readingObservable.observe(e)
  }
}
class SourceWatcher {
  sub: any
  constructor(e, t) {
    this.sub = t.volume.observe({
      notify() {
        e.add(t)
      }
    })
  }
  dispose() {
    this.sub.cancel()
  }
}

const g = (e, t) =>
    e.type === SourceTypeMin.BOX
      ? v(t)
      : e.type === SourceTypeMin.CYLINDER
      ? (function (e) {
          const t = new THREE.CylinderBufferGeometry(0.5, 0.5, 1)
          t.translate(0, 0.5, 0)
          const i = new THREE.Mesh(t, new THREE.MeshBasicMaterial())
          i.scale.set(0.05, 0.1, 0.05)
          const n = new THREE.MeshBasicMaterial({
              transparent: !0,
              opacity: 0.1,
              side: THREE.DoubleSide,
              depthWrite: !1
            }),
            s = new THREE.Mesh(t, new THREE.MeshBasicMaterial({ wireframe: !0 })),
            r = new THREE.Mesh(t, n)
          return (
            r.add(s),
            e.add(i),
            e.add(r),
            {
              interactionMesh: i,
              volumeMesh: r,
              dispose() {
                e.remove(i),
                  e.remove(r),
                  i.geometry.dispose(),
                  i.material.dispose(),
                  r.geometry.dispose(),
                  r.material.dispose(),
                  s.geometry.dispose(),
                  s.material.dispose()
              },
              setColor(e) {
                r.material.color.set(e), s.material.color.set(e)
              }
            }
          )
        })(t)
      : f(t),
  f = e => {
    const t = new THREE.SphereBufferGeometry(1, 20, 20),
      i = new THREE.Mesh(t, new THREE.MeshBasicMaterial())
    i.scale.set(0.5, 0.5, 0.5)
    const n = new THREE.Mesh(t, new THREE.MeshBasicMaterial({ wireframe: !0 })),
      s = new THREE.Mesh(
        t,
        new THREE.MeshBasicMaterial({
          transparent: !0,
          opacity: 0.1,
          side: THREE.DoubleSide,
          depthWrite: !1
        })
      )
    return (
      s.add(n),
      e.add(i),
      e.add(s),
      {
        interactionMesh: i,
        volumeMesh: s,
        dispose: () => {
          e.remove(i),
            e.remove(s),
            i.geometry.dispose(),
            i.material.dispose(),
            s.geometry.dispose(),
            s.material.dispose(),
            n.geometry.dispose(),
            n.material.dispose()
        },
        setColor: e => {
          s.material.color.set(e), n.material.color.set(e)
        }
      }
    )
  },
  v = e => {
    const t = new THREE.BoxBufferGeometry(1, 1, 1),
      i = new THREE.Mesh(t, new THREE.MeshBasicMaterial())
    i.scale.set(0.1, 0.1, 0.1)
    const n = new THREE.MeshBasicMaterial({
        transparent: !0,
        opacity: 0.1,
        side: THREE.DoubleSide,
        depthWrite: !1
      }),
      s = new THREE.Mesh(t, n),
      r = new THREE.LineSegments(new THREE.EdgesGeometry(t), new THREE.LineBasicMaterial())
    return (
      s.add(r),
      e.add(i),
      e.add(s),
      {
        interactionMesh: i,
        volumeMesh: s,
        dispose: () => {
          e.remove(i),
            e.remove(s),
            i.geometry.dispose(),
            i.material.dispose(),
            s.geometry.dispose(),
            s.material.dispose(),
            r.geometry.dispose(),
            r.material.dispose()
        },
        setColor: e => {
          s.material.color.set(e), r.material.color.set(e)
        }
      }
    )
  }
const y = e => {
    if (e.type === SourceTypeMin.BOX) {
      const t = e
      return Object.assign({}, t.volume.size)
    }
    if (e.type === SourceTypeMin.CYLINDER) {
      const t = e
      return {
        x: t.volume.radius,
        y: t.volume.height,
        z: t.volume.radius
      }
    }
    {
      const t = e.volume.radius
      return { x: t, y: t, z: t }
    }
  },
  b = new THREE.Quaternion().identity()
function S(e) {
  if (e.type === SourceTypeMin.BOX) {
    return e.volume.orientation
  }
  return b
}
class VolumeVisualizer {
  source: any
  scene: any
  inputIni: any
  subs: any[]
  stickyHover: boolean
  sourceDirty: boolean
  meshes:
    | {
        interactionMesh: THREE.Mesh<THREE.BoxGeometry, THREE.MeshBasicMaterial>
        volumeMesh: THREE.Mesh<THREE.BoxGeometry, THREE.MeshBasicMaterial>
        dispose: () => void
        setColor: (e: any) => void
      }
    | {
        interactionMesh: THREE.Mesh<THREE.CylinderGeometry, THREE.MeshBasicMaterial>
        volumeMesh: THREE.Mesh<THREE.CylinderGeometry, THREE.MeshBasicMaterial>
        dispose(): void
        setColor(e: any): void
      }
    | {
        interactionMesh: THREE.Mesh<THREE.SphereGeometry, THREE.MeshBasicMaterial>
        volumeMesh: THREE.Mesh<THREE.SphereGeometry, THREE.MeshBasicMaterial>
        dispose: () => void
        setColor: (e: any) => void
      }
  mixer: THREE.AnimationMixer
  onHoverAction: any
  constructor(e, t, i) {
    ;(this.source = e),
      (this.scene = t),
      (this.inputIni = i),
      (this.subs = []),
      (this.stickyHover = !1),
      (this.sourceDirty = !0),
      (this.meshes = g(e, this.scene.scene)),
      this.meshes.interactionMesh.scale.set(0.1, 0.1, 0.1),
      this.meshes.interactionMesh.position.copy(this.source.origin),
      this.meshes.interactionMesh.material.color.set(16724312),
      this.meshes.volumeMesh.scale.set(0, 0, 0),
      this.meshes.volumeMesh.position.copy(this.source.origin),
      (this.meshes.volumeMesh.visible = !1)
    const n = S(e)
    this.meshes.volumeMesh.quaternion.copy(n)
    this.mixer = new THREE.AnimationMixer(this.meshes.volumeMesh)
    const s = y(e),
      r = new THREE.VectorKeyframeTrack(".scale", [0, 0.4], [0, 0, 0, s.x, SymbolList, s.z], THREE.InterpolateSmooth),
      o = new THREE.AnimationClip(void 0, 0.4, [r])
    ;(this.onHoverAction = this.mixer.clipAction(o)),
      (this.onHoverAction.clampWhenFinished = !0),
      (this.onHoverAction.loop = THREE.LoopOnce),
      this.subs.push(
        this.inputIni.registerMeshHandler(HoverEvent, Comparator.isValue(this.meshes.interactionMesh), () => {
          const e = this.onHoverAction.time
          this.onHoverAction.reset(),
            (this.meshes.volumeMesh.visible = !0),
            (this.onHoverAction.timeScale = 1),
            (this.onHoverAction.time = e),
            this.onHoverAction.play()
        })
      ),
      this.subs.push(
        this.inputIni.registerMeshHandler(UnhoverEvent, Comparator.isValue(this.meshes.interactionMesh), () => {
          if (!this.stickyHover) {
            const e = this.onHoverAction.time
            this.onHoverAction.reset(), (this.onHoverAction.time = e), (this.onHoverAction.timeScale = -1), this.onHoverAction.play()
          }
        })
      ),
      this.subs.push(
        this.inputIni.registerMeshHandler(ClickEvent, Comparator.isValue(this.meshes.interactionMesh), () => {
          ;(this.stickyHover = !this.stickyHover),
            this.stickyHover ? this.meshes.interactionMesh.material.color.set(4473924) : this.meshes.interactionMesh.material.color.set(16724312)
        })
      ),
      this.inputIni.registerMesh(this.meshes.interactionMesh, !1),
      this.subs.push(e.volume.observe(this))
  }
  notify() {
    this.sourceDirty = !0
  }
  update(e, t) {
    if (this.sourceDirty) {
      ;(this.sourceDirty = !1),
        this.meshes.interactionMesh.position.copy(this.source.origin),
        this.meshes.volumeMesh.position.copy(this.source.origin),
        this.meshes.volumeMesh.quaternion.copy(S(this.source))
      const e = y(this.source),
        t = this.onHoverAction.getClip().tracks[0].values
      ;(t[3] = e.x), (t[4] = e.y), (t[5] = e.z)
    }
    this.meshes.setColor(t.inRange ? 65280 : 16711680), this.mixer.update(e / 1e3)
  }
  dispose() {
    this.meshes.dispose(), this.mixer.stopAllAction()
    for (const e of this.subs) e.cancel()
  }
}
class SensorRenderer {
  sensor: any
  scene: any
  inputIni: any
  subs: any[]
  visuals: Map<any, any>
  constructor(e, t, i) {
    ;(this.sensor = e), (this.scene = t), (this.inputIni = i), (this.subs = []), (this.visuals = new Map())
  }
  init() {
    this.subs.push(this.sensor.onReadingsUpdated(this))
  }
  render(e) {
    for (const [t, i] of this.sensor.readings) {
      let n = this.visuals.get(t)
      n || ((n = new VolumeVisualizer(t, this.scene, this.inputIni)), this.visuals.set(t, n)), n.update(e, i)
    }
  }
  dispose() {}
  activate() {}
  deactivate() {
    for (const e of this.subs) e.cancel()
    for (const e of this.visuals.values()) e.dispose()
    this.visuals.clear()
  }
  notify() {}
}
export default class SensorModule extends Module {
  sensorUpdater: SensorUpdater
  debugSensor: any
  sensorDisposer: any
  cameraData: any
  raycaster: any
  renderer: any
  inputIni: any
  cameraVolume: CameraFrustumVolume
  sensorDebuggerFactory: (e: any) => { dispose: () => void }
  constructor() {
    super(...arguments),
      (this.name = "sensor-module"),
      (this.sensorUpdater = new SensorUpdater()),
      (this.debugSensor = null),
      (this.sensorDisposer = new (class {
        sensors: any
        constructor(e) {
          this.sensors = e
        }
        disposeSensor(e) {
          this.sensors.removeSensor(e)
        }
      })(this))
  }
  async init(e, t) {
    ;([this.cameraData, this.raycaster, this.renderer, this.inputIni] = await Promise.all([
      await t.market.waitForData(CameraData),
      await t.getModule(RaycasterModule),
      await t.getModuleBySymbol(SymbolList.WEBGL_RENDERER),
      await t.getModuleBySymbol(SymbolList.INPUT)
    ])),
      (this.cameraVolume = new CameraFrustumVolume(this.cameraData))
    var i, n, r, o
    this.sensorDebuggerFactory =
      ((i = e => {
        t.addComponent(this, e)
      }),
      (n = e => {
        t.removeComponent(this, e)
      }),
      (r = this.renderer.getScene()),
      (o = this.inputIni),
      e => {
        const t = new SensorRenderer(e, r, o)
        return (
          i(t),
          {
            dispose: () => {
              n(t)
            }
          }
        )
      })
  }
  dispose() {
    this.cameraVolume.dispose()
  }
  createSensor(e) {
    const t = new SensorDecorator(e, this.raycaster, this.sensorDisposer)
    return this.sensorUpdater.addSensor(t), t
  }
  createCameraBoundSensor() {
    return this.createSensor(this.cameraVolume)
  }
  setDebugSensor(e) {
    this.debugSensor && (this.debugSensor.dispose(), (this.debugSensor = null)), e && (this.debugSensor = this.sensorDebuggerFactory(e))
  }
  onUpdate() {
    this.sensorUpdater.update()
  }
  removeSensor(e) {
    this.sensorUpdater.removeSensor(e)
  }
}
